﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.ComponentModel;

namespace ToolGab.Cleaner
{
    ///============================================================================================
    /// <summary>
    /// Manage the shortcuts
    /// </summary>
    ///============================================================================================
    class Shortcuts
    {
        #region EventDelegates
        public static event EventHandler RemoveInvalidShortcutCompleted;
        public static event RmInvSCProgressChangedEventHandler RemoveInvalidShortcutProgressChanged;

        #endregion

        #region Constants
        const int WORD_LENGTH = 2;
        const int DWORD_LENGTH = 4;
        const int QWORD_LENGTH = 8;
        const int GUID_LENGTH = 16;
        #endregion

        /// <summary>
        /// The GUID for shortcut
        /// </summary>
        private static Guid _shortcutGuid = new Guid("00021401-0000-0000-C000-000000000046");
        private static List<BackgroundWorker> m_bgwList = new List<BackgroundWorker>();

        ///----------------------------------------------------------------------------------------
        /// <summary>
        /// Number of process (BackgroundWorker) simultaneous running.
        /// </summary>
        ///----------------------------------------------------------------------------------------
        private static int m_CounterProcess = 0;

        ///----------------------------------------------------------------------------------------
        /// <summary>
        /// Remove the invalid shortcut (the one that have a wrong target), this function is ran
        /// asynchronously, make sure you set up event to follow progression.
        /// This will also clean sublfoders
        /// </summary>
        /// <param name="root">The root directory</param>
        ///----------------------------------------------------------------------------------------
        public static void RemoveInvalidShortcutASync(List<DirectoryInfo> dirList)
        {
            foreach (DirectoryInfo di in dirList)
            {
                BackgroundWorker bgw = new BackgroundWorker();

                bgw.WorkerReportsProgress = true;
                bgw.WorkerSupportsCancellation = true;

                bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
                bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
                bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);

                bgw.RunWorkerAsync(di);
                m_bgwList.Add(bgw);
                m_CounterProcess++;
            }
        }
        public static void CancelProcess()
        {
            foreach (BackgroundWorker bgw in m_bgwList)
            {
                bgw.CancelAsync();
            }
        }
        private static void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.UserState is RmInvSCProgressChangedEventArgs)
            {
                RmInvSCProgressChangedEventArgs fsi = (RmInvSCProgressChangedEventArgs)e.UserState;
                if (RemoveInvalidShortcutProgressChanged != null)
                {
                    RemoveInvalidShortcutProgressChanged(fsi);
                }
            }

        }

        static void bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            if (e.Argument is DirectoryInfo
                && sender is BackgroundWorker
                && !((BackgroundWorker)sender).CancellationPending)
            {
                DirectoryInfo di = (DirectoryInfo)e.Argument;
                BackgroundWorker bgw = (BackgroundWorker)sender;
                RemoveInvalidShortcut(di, bgw);
            }
        }
        static void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // throw new NotImplementedException();
            m_CounterProcess--;
            if (m_CounterProcess <= 0)
            {
                if (RemoveInvalidShortcutCompleted != null)
                {
                    RemoveInvalidShortcutCompleted(null, EventArgs.Empty);
                }
            }
            if (sender is BackgroundWorker)
            {
                m_bgwList.Remove((BackgroundWorker)sender);
            }

        }
        ///----------------------------------------------------------------------------------------
        /// <summary>
        /// Remove the invalid shortcut (the one that have a wrong target)
        /// This will also clean the sublfoder
        /// </summary>
        /// <param name="root">The root directory</param>
        ///----------------------------------------------------------------------------------------
        private static void RemoveInvalidShortcut(DirectoryInfo root, BackgroundWorker CurrentBGW)
        {
            if (CurrentBGW.CancellationPending)
                return;

            FileInfo[] files = null;
            DirectoryInfo[] subDirs = null;

            // First, process all the files directly under this folder
            try
            {
                files = root.GetFiles("*.lnk");
            }
            // This is thrown if even one of the files requires permissions greater
            // than the application provides.
            catch (UnauthorizedAccessException e)
            {
                // This code just writes out the message and continues to recurse.
                // You may decide to do something different here. For example, you
                // can try to elevate your privileges and access the file again.
                // log.Add(e.Message);
            }

            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
            }

            if (files != null)
            {
                foreach (FileInfo fi in files)
                {
                    if (CurrentBGW.CancellationPending)
                        return;
                    // Get the target file
                    string target = GetShortcutTarget(fi.FullName);

                    // Check if the target is not defined or if the string is not empty
                    if ((!File.Exists(target) && !Directory.Exists(target)) && target.Trim() != string.Empty)
                    {
                        //Console.WriteLine(target);
                        Console.WriteLine(fi.FullName);
                        Cleaner.CleanerEngine.DeleteFileOrFolder(fi);
                        CurrentBGW.ReportProgress(0, new RmInvSCProgressChangedEventArgs(fi, target));

                    }

                }

                // Now find all the subdirectories under this directory.
                subDirs = root.GetDirectories();

                foreach (DirectoryInfo dirInfo in subDirs)
                {
                    if (CurrentBGW.CancellationPending)
                        return;
                    // Resursive call for each subdirectory.
                    RemoveInvalidShortcut(dirInfo, CurrentBGW);
                }
                if (CurrentBGW.CancellationPending)
                    return;
                if (Properties.Settings.Default.ShortcutCleanerEraseEmptyFolder
                    && root.GetFiles().Length <= 0
                    && root.GetDirectories().Length <= 0
                    )
                {
                    Console.WriteLine(root.FullName);
                    Cleaner.CleanerEngine.DeleteFileOrFolder(root);
                    CurrentBGW.ReportProgress(0, new RmInvSCProgressChangedEventArgs(root, root.FullName));
                }
            }
        }


        private static string GetShortcutTarget(string path)
        {
            FileStream fs = new System.IO.FileStream(path, FileMode.Open, FileAccess.Read);
            BinaryReader reader = new BinaryReader(fs);
            AssertValidShortcutHeader(reader);

            HeaderFlags flags = (HeaderFlags)reader.ReadInt32();

            if ((flags & HeaderFlags.FileSystemShortcut) == 0)
                return string.Empty;

            //Go to the end of the shortcut header.
            fs.Seek(long.Parse("4C", NumberStyles.AllowHexSpecifier), SeekOrigin.Begin);

            //if this shortcut contains a ShellIDList, skip it.
            if ((flags & HeaderFlags.HasShellIDList) != 0)
            {
                int listLength = reader.ReadInt16();
                fs.Seek(listLength, SeekOrigin.Current);
            }

            //Note the beginning of the FileLocationInfo structure as all its offsets are relative.
            long fileLocationInfoOffset = fs.Position;
            int[] fileLocationInfo = GetFileLocationInfo(reader);

            string returnStr = "";
            switch ((VolumeType)(fileLocationInfo[2]))
            {
                case VolumeType.Local:

                    returnStr = GetLocalPath(reader, fileLocationInfo, fileLocationInfoOffset);
                    break;
                case VolumeType.Network:
                    returnStr = GetNetworkPath(reader, fileLocationInfo, fileLocationInfoOffset);
                    break;
                default:
                    throw new Exception("Unknown Volume Type");

            }
            fs.Close();
            return returnStr;

        }
        private static void AssertValidShortcutHeader(BinaryReader reader)
        {

            if (reader.ReadInt32() != long.Parse("4C", NumberStyles.AllowHexSpecifier) || new Guid(reader.ReadBytes(GUID_LENGTH)) != _shortcutGuid)
            {
                throw new Exception("Invalid or corrupt shortcut file");
            }
        }

        private static int[] GetFileLocationInfo(BinaryReader reader)
        {
            int[] info = new int[6];
            for (int i = 0; i < 6; i++)
            {
                info[i] = reader.ReadInt32();
            }

            return info;
        }

        private static string GetLocalPath(BinaryReader reader, int[] fileLocationInfo, long fileLocationInfoOffset)
        {
            int pathOffset = fileLocationInfo[4];
            reader.BaseStream.Seek(fileLocationInfoOffset + pathOffset, SeekOrigin.Begin);

            return ReadNullTerminatedString(reader);
        }

        private static string GetNetworkPath(BinaryReader reader, int[] fileLocationInfo, long fileLocationInfoOffset)
        {

            int pathOffset = fileLocationInfo[6];
            reader.BaseStream.Seek(fileLocationInfoOffset + pathOffset, SeekOrigin.Begin);

            string networkPath = ReadNullTerminatedString(reader);

            int shareInfoOffset = fileLocationInfo[5];
            reader.BaseStream.Seek(fileLocationInfoOffset + shareInfoOffset, SeekOrigin.Begin);
            reader.BaseStream.Seek(DWORD_LENGTH * 2, SeekOrigin.Current);

            int shareOffset = reader.ReadInt32();
            reader.BaseStream.Seek(fileLocationInfoOffset + shareInfoOffset + shareOffset, SeekOrigin.Begin);

            string shareName = ReadNullTerminatedString(reader);

            return shareName + @"\" + networkPath;
        }

        private static string ReadNullTerminatedString(BinaryReader reader)
        {
            List<Byte> bytes = new List<Byte>(16);
            while (reader.PeekChar() != -1)
            {
                Byte b = reader.ReadByte();
                if (b == 0)
                    break;

                bytes.Add(b);
            }


            return System.Text.Encoding.ASCII.GetString(bytes.ToArray());
        }

        [FlagsAttribute]
        enum HeaderFlags
        {
            HasShellIDList = 1,
            FileSystemShortcut = 2
        };

        enum VolumeType
        {
            Local = 1,
            Network = 2

        }
    }

    ///============================================================================================
    /// <summary>
    /// Event Handler
    /// </summary>
    /// <param name="RISCProgressChanged">Event arguments for the shortcuts class</param>
    ///============================================================================================
    public delegate void RmInvSCProgressChangedEventHandler
    (RmInvSCProgressChangedEventArgs RISCProgressChanged);
    ///============================================================================================
    /// <summary>
    /// Event arguments for the shortcuts class
    /// </summary>
    ///============================================================================================
    public class RmInvSCProgressChangedEventArgs
    {

        public RmInvSCProgressChangedEventArgs(FileSystemInfo ShortcutIOObject, string targetPath)
        {
            IOObject = ShortcutIOObject;
            TargetPath = targetPath;
        }
        public FileSystemInfo IOObject { get; private set; } // readonly
        public string TargetPath { get; private set; } // readonly

    }

}
